/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package czStvDpds.graphics3D.geometry;

import czStvDpds.graphics3D.GraphicsIndex;
import java.nio.FloatBuffer;
import java.nio.IntBuffer;
import java.util.ArrayList;
import org.lwjgl.BufferUtils;
import org.lwjgl.opengl.*;
import org.lwjgl.util.vector.Matrix4f;

/**
 * Objekt reprezentující zobrazovaný objekt.
 *
 * @author divad
 */
public class Object3D {

    private Mesh data;
    private ArrayList<Texture> textures;
    private OBB obb;
    public static final int NORMAL = 0, VBO = 1;
    private int type = NORMAL;
    private int dataIdentifier = 0;
    private int indicesIdentifier = 0;
    private Matrix4f posMatrix;
    private FloatBuffer posMatrixBuffer;
    private boolean generating = false;
    protected boolean enabled = true;

    /**
     * Vytvoří objekt
     *
     * @param data @ see data
     */
    public Object3D(Mesh data) {
        textures = new ArrayList<>();
        this.data = data;
        posMatrix = new Matrix4f();
        posMatrixBuffer = BufferUtils.createFloatBuffer(16);
        if (data.getType() == Mesh.VBO_MESH) {
            GraphicsIndex.getGLSynchronizer().generateVBO(this);
        }
    }

    protected Object3D(Mesh data, int dataIdentifier, int indicesIdentifier) {
        this.data = data;
        this.dataIdentifier = dataIdentifier;
        this.indicesIdentifier = indicesIdentifier;
        textures = new ArrayList<>();
        posMatrix = new Matrix4f();
        posMatrixBuffer = BufferUtils.createFloatBuffer(16);
        this.type = Object3D.VBO;
    }

    /**
     * Vykreslí objekt "normálním" způsobem. (glVertex3f(...))
     */
    private void drawNormal() {

        if (this.data.getType() == Mesh.NORMAL_MESH) {
            int usableTextureNumber = this.getUsableTexturesNumber();

            for (int index = 0; index < data.getVBOIndices().capacity(); index++) {
                int indice = data.getVBOIndices().get(index);
                int texCoordsStep = indice * 3 * this.getTextureCoordsNumber();

                if ((index % 3) == 0) {
                    GL11.glBegin(GL11.GL_TRIANGLES);
                }
                GL11.glNormal3f(data.getNormals().get(indice * 3), data.getNormals().get((indice * 3) + 1), data.getNormals().get((indice * 3) + 2));

                for (int i = 0; i < usableTextureNumber; i++) {
                    int coordsIndex = textures.get(i).getTextureCoordsIndex();
                    if (coordsIndex >= 0 && coordsIndex < this.getTextureCoordsNumber()) {
                        GL13.glMultiTexCoord3f(GL13.GL_TEXTURE0 + i, data.getTextureCoords().get(texCoordsStep + (3 * coordsIndex) + 0), data.getTextureCoords().get(texCoordsStep + (3 * coordsIndex) + 1), data.getTextureCoords().get(texCoordsStep + (3 * coordsIndex) + 2));
                    }
                }

                GL11.glVertex3f(data.getVertices().get(indice * 3 + 0), data.getVertices().get(indice * 3 + 1), data.getVertices().get(indice * 3 + 2));
                if ((index % 3) == 2) {
                    GL11.glEnd();
                }
            }
        }

    }

    /**
     * Vykreslí objekt jako VBO
     */
    private void drawVBO() {
        if (this.data.getType() == Mesh.VBO_MESH) {
            int stride = (3 + 3 + (data.getTextureCoordsNumber() * 3)) * 4;
            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, dataIdentifier);
            GL11.glVertexPointer(3, GL11.GL_FLOAT, stride, 0);
            GL11.glNormalPointer(GL11.GL_FLOAT, stride, 12);
            for (int i = 0; i < this.getUsableTexturesNumber(); i++) {
                int coordsIndex = textures.get(i).getTextureCoordsIndex();
                if (coordsIndex >= 0 && coordsIndex < this.getTextureCoordsNumber()) {
                    GraphicsIndex.getGLProperties().getTextureUnitSettings(i).setClientTextureEnabled(true);
                    GL11.glTexCoordPointer(3, GL11.GL_FLOAT, stride, 24 + (coordsIndex * 12));
                } else {
                    GraphicsIndex.getGLProperties().getTextureUnitSettings(i).setClientTextureEnabled(false);
                }
            }
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesIdentifier);
            GL12.glDrawRangeElements(GL11.GL_TRIANGLES, 0, data.getVboDataSet().capacity() / (6 + (data.getTextureCoordsNumber() * 3)), data.getVBOIndices().capacity(), GL11.GL_UNSIGNED_INT, 0);
        }
    }

    /**
     * Nastaví vykreslování objektu a vykreslí ho.
     */
    public void draw() {
        predrawSetup();
        posMatrix.store(posMatrixBuffer);
        posMatrixBuffer.rewind();
        GL11.glMatrixMode(GL11.GL_MODELVIEW);
        GL11.glPushMatrix();
        GL11.glMultMatrix(posMatrixBuffer);

        for (int i = 0; i < 32; i++) {
            if (i < this.textures.size() && textures.get(i).getTextureCoordsIndex() >= 0 && textures.get(i).getTextureCoordsIndex() < this.data.getTextureCoordsNumber()) {
//                System.out.println("use in object");
//                GraphicsIndex.getGLProperties().getTextureUnitSettings(i).setClientTextureEnabled(true);
                textures.get(i).use(GraphicsIndex.getGLProperties().getTextureUnitSettings(i));
            } else {
                GraphicsIndex.getGLProperties().getTextureUnitSettings(i).disable();
                GraphicsIndex.getGLProperties().getTextureUnitSettings(i).setClientTextureEnabled(false);
            }
        }

        if (type == NORMAL) {
            if (data.getType() == Mesh.NORMAL_MESH) {
                this.drawNormal();
            } else {
                this.toNormal();
            }
        } else if (type == VBO) {
            if (data.getType() == Mesh.VBO_MESH) {
                this.generating = false;
                this.drawVBO();
            } else if (this.generating == false) {
                GraphicsIndex.getGLSynchronizer().generateVBO(this);
                this.generating = true;
            }
        }

        secondaryDraw();
        GL11.glPopMatrix();
    }

    protected void secondaryDraw() {
    }

    protected void predrawSetup() {
    }

    /**
     * Vrátí počet použitelných textur.
     *
     * @return
     */
    public int getUsableTexturesNumber() {
        return textures.size();
    }

    /**
     * Vrátí počet sad texturových koordinátů.
     *
     * @return
     */
    public int getTextureCoordsNumber() {
        return data.getTextureCoordsNumber();
    }

    /**
     * Převede objekt na "normální" (zobrazovaný pomocí glVertex3f(...))
     *
     * @return
     */
    public void toNormal() {
        this.type = Object3D.NORMAL;
    }

    /**
     * Převede objekt na VBO (zobrazovaný pomocí VBO). Mělo by být voláno pouze
     * hlavní vykreslovací jednotkou.
     *
     * @return vrací true pokud bylo vytvoření VBO úspěšné
     */
    public void toVBO() {
        IntBuffer buffer = BufferUtils.createIntBuffer(2);
        GL15.glGenBuffers(buffer);
        this.dataIdentifier = buffer.get(0);
        this.indicesIdentifier = buffer.get(1);

        GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, dataIdentifier);
        GL15.glBufferData(GL15.GL_ARRAY_BUFFER, data.getVboDataSet(), GL15.GL_STATIC_DRAW);

        GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesIdentifier);
        GL15.glBufferData(GL15.GL_ELEMENT_ARRAY_BUFFER, data.getVBOIndices(), GL15.GL_STATIC_DRAW);

        this.type = Object3D.VBO;
    }

    public void changeVBOData(int offset, FloatBuffer data) {
        this.data.changeVBODataSet(offset, data);
        if (this.type == Object3D.VBO) {
            GL15.glBindBuffer(GL15.GL_ARRAY_BUFFER, dataIdentifier);
            GL15.glBufferSubData(GL15.GL_ARRAY_BUFFER, offset, data);
        }
    }

    public void changeVBOIndices(int offset, IntBuffer indices) {
        this.data.changeVBOIndices(offset, indices);
        if (this.type == Object3D.VBO) {
            GL15.glBindBuffer(GL15.GL_ELEMENT_ARRAY_BUFFER, indicesIdentifier);
            GL15.glBufferSubData(GL15.GL_ELEMENT_ARRAY_BUFFER, offset, indices);
        }
    }

    /**
     * Vrátí geometrii objektu
     *
     * @return
     */
    public Mesh getData() {
        return data;
    }

    /**
     * Vrátí OBB objektu
     *
     * @return
     */
    public OBB getObb() {
        return obb;
    }

    /**
     * Vrátí pole textur přidaných objektu
     *
     * @return
     */
    public ArrayList<Texture> getTextures() {
        return textures;
    }

    /**
     * Vrátí typ objektu (normal,VBO)
     *
     * @return
     */
    public int getType() {
        return type;
    }

    /**
     * Vrátí transformační matici objektu
     *
     * @return
     */
    public Matrix4f getPosMatrix() {
        return posMatrix;
    }

    public int getDataIdentifier() {
        return dataIdentifier;
    }

    public boolean isGenerating() {
        return generating;
    }

    public int getIndicesIdentifier() {
        return indicesIdentifier;
    }

    /**
     * Přidá texturu objektu
     *
     * @param texture
     * @param index
     */
    public void addTexture(Texture texture, int index) {
        if (index < 0 || index > this.textures.size()) {
            this.textures.add(texture);
        } else {
            this.textures.add(index, texture);
        }
    }

    public void removeTexture(Texture texture) {
        this.textures.remove(texture);
    }

    /**
     * Vytvoří nový 3D objekt naprosto nezávislý na původním objektu.
     *
     * @return
     */
    public Object3D copyObjectValue() {
        Object3D object = new Object3D(new Mesh(this.data));
        for (Texture t : textures) {
            object.addTexture(t.copyTextureValue(), -1);
        }
        object.posMatrix = new Matrix4f().load(posMatrix);
        object.dataIdentifier = this.dataIdentifier;
        object.indicesIdentifier = this.indicesIdentifier;
        object.type = this.type;
        return object;
    }

    /**
     * Vytvoří objekt který má s původním objektem společná data a jejich GL
     * ukazatele. Textury nového objektu budou závislé na texturách původního.
     *
     * @return
     */
    public Object3D copyObjectReference() {
        Object3D object = new Object3D(this.data, this.dataIdentifier, this.indicesIdentifier);
        object.posMatrix = this.posMatrix;
        for (Texture t : textures) {
            object.addTexture(t.copyTextureReference(), -1);
        }
        object.type = this.type;
        return object;
    }

    public void setPosMatrix(Matrix4f posMatrix) {
        this.posMatrix = posMatrix;
    }

    public boolean isEnabled() {
        return enabled;
    }

    public void setEnabled(boolean enabled) {
        this.enabled = enabled;
    }
}
